﻿using System;
using System.IO;
using System.Text;
using System.Collections.Generic;

using System.Drawing;

using RSEngine.Core;
using RSEngine.Math;

#region Copyright (c) 2006, 2007, 2008, 2009 Render Studio Foundation. All rights reserved.

/*
 Copyright (c) 2006, 2007, 2008, 2009 Render Studio Foundation. All rights reserved.
 This work is copyrighted in its entirety. Permission to use "Render Studio"
 material may be granted only after we receive a written request from you,
 and you receive written permission from us.

 While Render Studio Foundation provides the information herein to anyone,
 we retain copyright on all code, text, graphic images, and other content.
 This means that you may not distribute the code, the text or graphics to others without the
 express written permission of Render Studio Foundation; "mirror" this information on your server
 without our permission; or modify or reuse the code, text or graphics on our servers.
 You may print copies of the information for your own personal use; and, you may reference
 the Service from your own documents. Any commercial use of this material is prohibited without
 the written permission of Render Studio Foundation. In all copies of this information, you must
 retain this notice and any other copyright notices originally included with such information.

 This material may be protected by U.S. and International Copyright laws, and you are legally
 bound by the copyright notices on those systems.

 "Render Studio" logos and other official symbols or graphics may not be used in connection with any
 product or service that has not been expressly authorized in advance in writing or in any manner
 that is likely to cause confusion among the users of this or other Web sites or among the public.
 All other seals, graphics or trademarks of organizations not affiliated with Render Studio Foundation
 that appear on this site are the property of their respective owners.

 Render Studio Foundation reserve all other rights. 
*/

#endregion

#region Render Studio Source Code Licence

/*
This license governs use of the Render Studio software. If you use the software, you accept this license. If you do not accept the license, do not use the software.

1. Summary

The source code is made available to view for reference purposes or to contribute to the project only. Developers may not distribute or modify the code for commercial or non-commercial purposes without express Agreement of Render Studio Foundation.

2. Definitions

The terms "reproduce," "reproduction," and "distribution" have the same meaning here as under U.S. copyright law.
"Licensor" means the foundation "Render Studio Foundation".
"You" means the licensee of the software, who is not engaged in designing, developing, or testing other software, that has the same or substantially the same features or functionality as the software.
"Your company" means the company you worked for when you downloaded the software.
"Reference use" means use of the software within your company as a reference, in read only form, for the sole purposes of debugging and maintaining your products. For clarity, "reference use" does NOT include (a) the right to use the software for purposes of designing, developing, or testing other software, that has the same or substantially the same features or functionality as the software, and (b) the right to distribute the software outside of your company.
"Licensed patents" means any Licensor patent claims which read directly on the software as distributed by the Licensor under this license.

3. Grant of Rights

(A) Copyright Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, worldwide, royalty-free copyright license to reproduce the software for reference use.
(B) Patent Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, worldwide, royalty-free patent license under licensed patents for reference use.
4. Limitations
(A) No Trademark License- This license does not grant you any rights to use the Licensor's name, logo, or trademarks.
(B) If you begin patent litigation against the Licensor over patents that you think may apply to the software (including a cross-claim or counterclaim in a lawsuit), your license to the software ends automatically.
(C) The software is licensed "as-is." You bear the risk of using it. The Licensor gives no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the Licensor excludes the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
 */

#endregion

namespace RSEngine.FilesFormats._3DS
{
    // Reference : http://www.the-labs.com/Blender/3DS-details.html
    // http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/3ds/

    // Source code :
    // http://code.google.com/p/salmon-viewer/
    // http://www.codeproject.com/KB/graphics/avalon3ds.aspx
    // http://www.wpf-graphics.com/Reader3ds.aspx
    // http://max2xaml.codeplex.com/

    /// <summary>
    /// This class is responsible to import ".3DS" files.
    /// </summary>
    sealed public class Importer
    {

        #region Variables

        //---- Scene
        private float _scaleScene;
        private Scene _scene;

        //----
        private Stack<Transformable> _hierarchy = new Stack<Transformable>();

        //---- Materials
        private Dictionary<string, Material> materials = new Dictionary<string, Material>();

        //----
        BinaryReader reader;

        #endregion

        #region ImportFile

        public bool ImportFile(Scene scene, string fileName, double scaleScene)
        {
            _scene = scene;
            _scaleScene = (float)scaleScene;

            if (string.IsNullOrEmpty(fileName))
                throw new ArgumentNullException("file_name");

            if (!File.Exists(fileName))
                throw new ArgumentException("3ds file could not be found", "file_name");

            // 3ds models can use additional files which are expected in the same directory
            //base_dir = new FileInfo(fileName).DirectoryName + "/";

            //---- Read the file
            FileStream file = null;
            try
            {
                // create a binary stream from this file
                file = new FileStream(fileName, FileMode.Open, FileAccess.Read);
                reader = new BinaryReader(file);
                reader.BaseStream.Seek(0, SeekOrigin.Begin);

                Process_Main3DS();
            }
            catch (Exception e)
            {
            }
            finally
            {
                // close up everything
                if (reader != null) reader.Close();
                if (file != null) file.Close();
            }

            return true;
        }

        #endregion

        #region Process_Main3DS

        private void Process_Main3DS()
        {
            //---- Read the main
            Chunk chunk = new Chunk(reader);

            if (chunk.ID != (short)ChunksIds.MAIN3DS)
                throw new FormatException("Not a proper 3DS file.");

            //---- File version
            Chunk vChunk = new Chunk(reader);
            chunk.BytesRead += 6;

            int version = reader.ReadInt32();
            chunk.BytesRead += 4;

            if (version < 3)
                throw new FormatException("The version of the file is not supported.");

            //---- recursively process chunks
            //chunk = new Chunk(reader);
            ProcessChunk_Main(chunk);
        }

        #endregion

        #region ProcessChunk_Main

        private void ProcessChunk_Main(Chunk chunk)
        {
            // Process chunks until there are none left
            while (chunk.BytesRead < chunk.Length)
            {
                Chunk child = new Chunk(reader);
                switch ((ChunksIds)child.ID)
                {
                    case ChunksIds.KEYF3DS:
                        //ProcessChunk_KEYF3DS(child); <-- CRASH !!
                        SkipChunk(child);
                        break;

                    case ChunksIds.EDIT3DS:
                        ProcessChunk_EDIT3DS(child);
                        break;
                    default:
                        SkipChunk(child);
                        break;
                }

                // Update the chunk position
                chunk.BytesRead += child.BytesRead;
            }
        }

        #endregion

        #region ProcessChunk_KEYF3DS

        private void ProcessChunk_KEYF3DS(Chunk chunk)
        {
            // Process chunks until there are none left
            while (chunk.BytesRead < chunk.Length)
            {
                Chunk child = new Chunk(reader);
                switch ((ChunksIds)child.ID)
                {
                    case ChunksIds.KEYF_OBJDES:
                        SkipChunk(child);
                        //ProcessChunk_KEYF_OBJDES(child);
                        break;

                    case ChunksIds.EDIT_VIEW1:
                        SkipChunk(child);
                        //ProcessChunk_EDIT_VIEW1(child);
                        break;
                    default:
                        SkipChunk(child);
                        break;
                }

                // Update the chunk position
                chunk.BytesRead += child.BytesRead;
            }
        }

        #endregion

        #region ProcessChunk_EDIT3DS

        private void ProcessChunk_EDIT3DS(Chunk chunk)
        {
            // Process chunks until there are none left
            while (chunk.BytesRead < chunk.Length)
            {
                Chunk child = new Chunk(reader);
                switch ((ChunksIds)child.ID)
                {
                    case ChunksIds.EDIT_MATERIAL:
                        ProcessChunk_EDIT_MATERIAL(child);
                        break;
                    case ChunksIds.EDIT_VIEW1:
                        ProcessChunk_EDIT_VIEW1(child);
                        break;
                    case ChunksIds.EDIT_BACKGR:
                        ProcessChunk_EDIT_BACKGR(child);
                        break;
                    case ChunksIds.EDIT_AMBIENT:
                        ProcessChunk_EDIT_AMBIENT(child);
                        break;
                    case ChunksIds.EDIT_OBJECT:
                        Node node = new Node(_scene.Layers[0]);
                        node.Name = ProcessString(child);
                        _scene.Layers[0].Nodes.Add(node);

                        _hierarchy.Push(node);

                        ProcessChunk_EDIT_OBJECT(child);

                        _hierarchy.Pop();
                        break;

                    case ChunksIds.EDIT_UNKNW01:
                    case ChunksIds.EDIT_UNKNW02:
                    case ChunksIds.EDIT_UNKNW03:
                    case ChunksIds.EDIT_UNKNW04:
                    case ChunksIds.EDIT_UNKNW05:
                    case ChunksIds.EDIT_UNKNW06:
                    case ChunksIds.EDIT_UNKNW07:
                    case ChunksIds.EDIT_UNKNW08:
                    case ChunksIds.EDIT_UNKNW09:
                    case ChunksIds.EDIT_UNKNW10:
                    case ChunksIds.EDIT_UNKNW11:
                    case ChunksIds.EDIT_UNKNW12:
                    case ChunksIds.EDIT_UNKNW13:

                    default:
                        SkipChunk(child);
                        break;
                }

                // Update the chunk position
                chunk.BytesRead += child.BytesRead;
            }
        }

        #endregion

        #region ProcessChunk_EDIT_MATERIAL

        private void ProcessChunk_EDIT_MATERIAL(Chunk chunk)
        {
            Material material = null;

            // Process chunks until there are none left
            while (chunk.BytesRead < chunk.Length)
            {
                Chunk child = new Chunk(reader);
                switch ((ChunksIds)child.ID)
                {
                    case ChunksIds.MAT_NAME01:
                        material = new Material();
                        material.Name = ProcessString(child);
                        break;
                    case ChunksIds.MAT_AMBIENT:
                        material.Ambient = new ColorMap(ProcessColorRGB(child));
                        break;

                    case ChunksIds.MAT_DIFFUSE:
                        material.Diffuse = new ColorMap(ProcessColorRGB(child));
                        break;

                    case ChunksIds.MAT_SPECULAR:
                        material.Specular = new ColorMap(ProcessColorRGB(child));
                        break;

                    case ChunksIds.MAT_SHININESS:
                        material.Shininess = ProcessPercentageChunk(child);
                        break;

                    //case Groups.MAT_MAP:
                    //ProcessPercentageChunk(child);
                    //ProcessTexMapChunk(child, m);
                    //SkipChunk(child);
                    //break;
                    default:
                        SkipChunk(child);
                        break;
                }

                // Update the chunk position
                chunk.BytesRead += child.BytesRead;
            }

            materials.Add(material.Name, material);
        }

        #endregion

        #region ProcessChunk_EDIT_VIEW1

        private void ProcessChunk_EDIT_VIEW1(Chunk chunk)
        {
            // Process chunks until there are none left
            while (chunk.BytesRead < chunk.Length)
            {
                Chunk child = new Chunk(reader);
                switch ((ChunksIds)child.ID)
                {
                    case ChunksIds.EDIT_VIEW_P1:
                    //ProcessChunk_ViewPort(child);
                    //break;
                    case ChunksIds.EDIT_VIEW_P2:
                    //SkipChunk(child);
                    //break;
                    case ChunksIds.EDIT_VIEW_P3:
                    //ProcessChunk_ViewPort(child);
                    //break;
                    default:
                        SkipChunk(child);
                        break;
                }

                // Update the chunk position
                chunk.BytesRead += child.BytesRead;
            }
        }

        #endregion

        #region ProcessChunk_EDIT_BACKGR

        private void ProcessChunk_EDIT_BACKGR(Chunk chunk)
        {
            // Process chunks until there are none left
            while (chunk.BytesRead < chunk.Length)
            {
                Chunk child = new Chunk(reader);
                switch ((ChunksIds)child.ID)
                {
                    case ChunksIds.COL_RGB:
                        float[] colorRGB = ProcessColorRGB(child);
                        break;
                    case ChunksIds.COL_TRU:
                        float[] color24 = ProcessColor24(child);
                        break;
                    default:
                        SkipChunk(child);
                        break;
                }

                // Update the chunk position
                chunk.BytesRead += child.BytesRead;
            }
        }

        #endregion

        #region ProcessChunk_EDIT_AMBIENT

        private void ProcessChunk_EDIT_AMBIENT(Chunk chunk)
        {
            // Process chunks until there are none left
            while (chunk.BytesRead < chunk.Length)
            {
                Chunk child = new Chunk(reader);
                switch ((ChunksIds)child.ID)
                {
                    case ChunksIds.COL_RGB:
                        float[] colorRGB = ProcessColorRGB(child);
                        break;
                    case ChunksIds.COL_TRU:
                        float[] color24 = ProcessColor24(child);
                        break;
                    default:
                        SkipChunk(child);
                        break;
                }

                // Update the chunk position
                chunk.BytesRead += child.BytesRead;
            }
        }

        #endregion

        #region ProcessChunk_EDIT_OBJECT

        private void ProcessChunk_EDIT_OBJECT(Chunk chunk)
        {
            // Process chunks until there are none left
            while (chunk.BytesRead < chunk.Length)
            {
                Chunk child = new Chunk(reader);
                switch ((ChunksIds)child.ID)
                {
                    case ChunksIds.OBJ_TRIMESH:
                        Node parentNode = (Node)_hierarchy.Peek();

                        MeshGeometry triangle = new MeshGeometry(parentNode, 3);
                        parentNode.Geometries.Add(triangle);

                        _hierarchy.Push(triangle);

                        ProcessChunk_OBJ_TRIMESH(child);

                        _hierarchy.Pop();
                        break;
                    case ChunksIds.OBJ_LIGHT:
                    //break;
                    case ChunksIds.OBJ_CAMERA:
                    //break;
                    case ChunksIds.OBJ_UNKNWN01:
                    case ChunksIds.OBJ_UNKNWN02:
                    default:
                        SkipChunk(child);
                        break;
                }

                // Update the chunk position
                chunk.BytesRead += child.BytesRead;
            }
        }

        #endregion

        #region ProcessChunk_OBJ_TRIMESH

        private void ProcessChunk_OBJ_TRIMESH(Chunk chunk)
        {
            List<MaterialGroup> materialGroups = new List<MaterialGroup>();
            MeshGeometry mesh;

            // Process chunks until there are none left
            while (chunk.BytesRead < chunk.Length)
            {
                Chunk child = new Chunk(reader);
                switch ((ChunksIds)child.ID)
                {
                    case ChunksIds.TRI_VERTEXL:
                        ReadVertices(child, (MeshGeometry)_hierarchy.Peek());
                        break;
                    case ChunksIds.TRI_FACEL1:
                        ReadIndices(child, (MeshGeometry)_hierarchy.Peek());
                        break;
                    case ChunksIds.TRI_FACEL2:
                        SkipChunk(child);
                        break;
                    case ChunksIds.TRI_MATERIAL:
                        string name = ProcessString(child);

                        Material mat;
                        if (!materials.TryGetValue(name, out mat))
                            throw new Exception("Material not found!");

                        //mesh3.Material = mat;

                        int facesCount = reader.ReadUInt16();
                        child.BytesRead += 2;

                        if (facesCount > 0)
                        {
                            MaterialGroup group = new MaterialGroup();
                            materialGroups.Add(group);
                            group.Material = mat;

                            for (int ii = 0; ii < facesCount; ii++)
                            {
                                group.FacesIds.Add(reader.ReadUInt16());
                                child.BytesRead += 2;
                            }
                        }
                        //SkipChunk(child);
                        break;
                    case ChunksIds.TRI_LOCAL:
                        ProcessChunk_TRI_LOCAL(child);
                        break;
                    case ChunksIds.TRI_VISIBLE:
                    case ChunksIds.TRI_SMOOTH:
                        ProcessChunk_TRI_SMOOTH(child);
                        break;
                    //ReadBooleanChunk(&boolean);
                    default:
                        SkipChunk(child);
                        break;
                }

                // Update the chunk position
                chunk.BytesRead += child.BytesRead;
            }

            mesh = (MeshGeometry)_hierarchy.Peek();
            mesh.Update();
            if (materialGroups.Count > 0)
                DecomposeMesh(mesh, materialGroups);
        }

        #endregion

        #region DecomposeMesh

        private void DecomposeMesh(MeshGeometry parent, List<MaterialGroup> materialGroups)
        {
            List<MeshGeometry> triangles = new List<MeshGeometry>();
            foreach (MaterialGroup group in materialGroups)
            {
                MeshGeometry triangle = new MeshGeometry(parent, 3);
                triangle.Material = group.Material;
                triangles.Add(triangle);

                int index = 0;
                foreach (int faceId in group.FacesIds)
                {
                    int indice = faceId * 3;

                    int vIndex = parent.Indices[indice];
                    triangle.Vertices.Add(parent.Vertices[vIndex]);
                    triangle.Indices.Add(index);
                    index++;

                    vIndex = parent.Indices[indice + 1];
                    triangle.Vertices.Add(parent.Vertices[vIndex]);
                    triangle.Indices.Add(index);
                    index++;

                    vIndex = parent.Indices[indice + 2];
                    triangle.Vertices.Add(parent.Vertices[vIndex]);
                    triangle.Indices.Add(index);
                    index++;
                }
            }

            //---- Update the parent with several childs
            parent.ClearGeometry();
            foreach (MeshGeometry triangle in triangles)
                parent.Children.Add(triangle);

            parent.Update();
        }

        #endregion

        #region ProcessChunk_TRI_LOCAL

        //private void ProcessChunk_TRI_LOCAL(Chunk chunk)
        //{
        //    Matrix4 matrix = Matrix4.Zero.Clone();

        //    for (int j = 0; j < 4; j++)
        //        for (int i = 0; i < 3; i++)
        //            matrix.Values[j * 4 + i] = reader.ReadSingle() * _scaleScene;

        //    matrix.M03 = 0;
        //    matrix.M13 = 0;
        //    matrix.M23 = 0;
        //    matrix.M33 = 1;

        //    chunk.BytesRead += 3 * 4 * sizeof(Single);

        //    MeshGeometry geometry = (MeshGeometry)_hierarchy.Peek();
        //    geometry.Translation = matrix;
        //}

        //private void ProcessChunk_TRI_LOCAL2(Chunk chunk)
        //{
        //    double num20 = reader.ReadSingle()* _scaleScene;
        //    double num21 = reader.ReadSingle()* _scaleScene;
        //    double num22 = reader.ReadSingle()* _scaleScene;
        //    double num23 = 0.0;
        //    double num24 = reader.ReadSingle()* _scaleScene;
        //    double num25 = reader.ReadSingle()* _scaleScene;
        //    double num26 = reader.ReadSingle()* _scaleScene;
        //    double num27 = 0.0;
        //    double num28 = reader.ReadSingle()* _scaleScene;
        //    double num29 = reader.ReadSingle()* _scaleScene;
        //    double num30 = reader.ReadSingle()* _scaleScene;
        //    double num31 = 0.0;
        //    double num32 = reader.ReadSingle()* _scaleScene;
        //    double num33 = reader.ReadSingle()* _scaleScene;
        //    double num34 = reader.ReadSingle()* _scaleScene;
        //    double num35 = 1.0;

        //    Matrix4 matrix = new Matrix4(num20, num21, num22, num23, num24, num25, num26, num27, num28, num29, num30, num31, num32, num33, num34, num35);
        //    MeshGeometry geometry = (MeshGeometry)_hierarchy.Peek();
        //    geometry.Translation = matrix;
        //}

        /** 
        * If readNextChunk returned a data type of transform matrix,
        * this function is used to read in the transform.
        * Vertices are added to a SubMesh in this function.
        */
        private void ProcessChunk_TRI_LOCAL(Chunk chunk)
        {
            MeshGeometry mesh = (MeshGeometry)_hierarchy.Peek();

            // fill the TempMatrix with data from the 3ds file
            float[] TempMatrix = new float[12];
            for (int i = 0; i < 12; i++)
                TempMatrix[i] = reader.ReadSingle();

            // fill the Position Vector with data read from the Matrix
            float dX = TempMatrix[9];   // tX
            float dY = TempMatrix[11];  // tZ
            float dZ = -TempMatrix[10];  // tY
            TranslateMesh(mesh, dX * _scaleScene, dY * _scaleScene, dZ * _scaleScene);
        }

        private void TranslateMesh(MeshGeometry mesh, float dX, float dY, float dZ)
        {
            foreach (MeshGeometry subMesh in mesh.Children)
                TranslateMesh(subMesh, dX, dY, dZ);

            foreach (MeshGeometry geometry in mesh.Children)
                for (int index = 0; index < geometry.Vertices.Count; index++)
                {
                    Vector3 vertex = geometry.Vertices[index].Coordinate;
                    vertex.x += dX;
                    vertex.y += dY;
                    vertex.z += dZ;

                    geometry.Vertices.Items[index].Coordinate = vertex;
                }
        }

        #endregion

        #region ProcessChunk_TRI_SMOOTH

        private void ProcessChunk_TRI_SMOOTH(Chunk chunk)
        {
            List<UInt16> smoothGroups = new List<UInt16>();
            while (chunk.BytesRead < chunk.Length)
            {
                smoothGroups.Add(reader.ReadByte());
                smoothGroups.Add(reader.ReadByte());
                smoothGroups.Add(reader.ReadByte());
                smoothGroups.Add(reader.ReadByte());

                // Update the chunk position
                chunk.BytesRead += 4;
            }
        }

        #endregion

        #region ReadVertices

        private void ReadVertices(Chunk chunk, MeshGeometry geometry)
        {
            ushort numVerts = reader.ReadUInt16();
            chunk.BytesRead += 2;

            for (int ii = 0; ii < numVerts; ii++)
            {
                float f1 = reader.ReadSingle() * _scaleScene;
                float f2 = reader.ReadSingle() * _scaleScene;
                float f3 = reader.ReadSingle() * _scaleScene;

                Vertex vertex = Vertex.Invalid;
                vertex.Coordinate = new Vector3(f1, f3, -f2); // new Vector3(f1, f2, f3)
                geometry.Vertices.Add(vertex);
            }

            chunk.BytesRead += numVerts * (3 * sizeof(float));
        }

        #endregion

        #region ReadIndices

        private void ReadIndices(Chunk chunk, MeshGeometry geometry)
        {
            ushort numIdcs = reader.ReadUInt16();
            chunk.BytesRead += 2;

            for (int ii = 0; ii < numIdcs; ii++)
            {
                geometry.Indices.Add(reader.ReadUInt16());
                geometry.Indices.Add(reader.ReadUInt16());
                geometry.Indices.Add(reader.ReadUInt16());

                // flags
                reader.ReadUInt16();
            }
            chunk.BytesRead += (sizeof(ushort) * 4) * numIdcs;
        }

        #endregion

        #region Helpers

        private float[] ProcessColor24(Chunk chunk)
        {
            Chunk child = new Chunk(reader);
            float[] c = new float[] { (float)reader.ReadSingle(), (float)reader.ReadSingle(), (float)reader.ReadSingle() };

            chunk.BytesRead += (int)child.Length;
            return c;
        }

        private float[] ProcessColorRGB(Chunk chunk)
        {
            Chunk child = new Chunk(reader);
            float[] c = new float[] { (float)reader.ReadByte() / 256, (float)reader.ReadByte() / 256, (float)reader.ReadByte() / 256, 1f };

            chunk.BytesRead += (int)child.Length;
            return c;
        }

        private int ProcessPercentageChunk(Chunk chunk)
        {
            Chunk child = new Chunk(reader);
            int per = reader.ReadUInt16();
            child.BytesRead += 2;
            chunk.BytesRead += child.BytesRead;
            return per;
        }

        private void SkipChunk(Chunk chunk)
        {
            int length = (int)chunk.Length - chunk.BytesRead;
            reader.ReadBytes(length);
            chunk.BytesRead += length;
        }

        private string ProcessString(Chunk chunk)
        {
            StringBuilder sb = new StringBuilder();

            byte b = reader.ReadByte();
            int idx = 0;
            while (b != 0)
            {
                sb.Append((char)b);
                b = reader.ReadByte();
                idx++;
            }
            chunk.BytesRead += idx + 1;

            return sb.ToString();
        }

        #endregion

    }

    #region ChunksIds

    enum ChunksIds
    {
        //---- Id Chunk
        MAIN3DS = 0x4D4D,

        //---- Main Chunks
        EDIT3DS = 0x3D3D,  // this is the start of the editor config
        KEYF3DS = 0xB000,  // this is the start of the keyframer config

        //---- sub defines of EDIT3DS

        EDIT_MATERIAL = 0xAFFF,
        EDIT_CONFIG1 = 0x0100,
        EDIT_CONFIG2 = 0x3E3D,
        EDIT_VIEW_P1 = 0x7012,
        EDIT_VIEW_P2 = 0x7011,
        EDIT_VIEW_P3 = 0x7020,
        EDIT_VIEW1 = 0x7001,
        EDIT_BACKGR = 0x1200,
        EDIT_AMBIENT = 0x2100,
        EDIT_OBJECT = 0x4000,

        EDIT_UNKNW01 = 0x1100,
        EDIT_UNKNW02 = 0x1201,
        EDIT_UNKNW03 = 0x1300,
        EDIT_UNKNW04 = 0x1400,
        EDIT_UNKNW05 = 0x1420,
        EDIT_UNKNW06 = 0x1450,
        EDIT_UNKNW07 = 0x1500,
        EDIT_UNKNW08 = 0x2200,
        EDIT_UNKNW09 = 0x2201,
        EDIT_UNKNW10 = 0x2210,
        EDIT_UNKNW11 = 0x2300,
        EDIT_UNKNW12 = 0x2302, // new chunk type
        EDIT_UNKNW13 = 0x3000,
        EDIT_UNKNW14 = 0xAFFF,

        //---- sub defines of EDIT_MATERIAL
        MAT_NAME01 = 0xA000,  // includes name (see mli doc for materials)
        MAT_AMBIENT = 0xA010,
        MAT_DIFFUSE = 0xA020,
        MAT_SPECULAR = 0xA030,
        MAT_SHININESS = 0xA040,

        //---- sub defines of EDIT_OBJECT
        OBJ_TRIMESH = 0x4100,
        OBJ_LIGHT = 0x4600,
        OBJ_CAMERA = 0x4700,

        OBJ_UNKNWN01 = 0x4010,
        OBJ_UNKNWN02 = 0x4012, //>>---- Could be shadow

        //---- sub defines of OBJ_CAMERA
        CAM_UNKNWN01 = 0x4710, // new chunk type
        CAM_UNKNWN02 = 0x4720, // new chunk type

        //---- sub defines of OBJ_LIGHT
        LIT_OFF = 0x4620,
        LIT_SPOT = 0x4610,
        LIT_UNKNWN01 = 0x465A,

        //---- sub defines of OBJ_TRIMESH
        TRI_VERTEXL = 0x4110,
        TRI_FACEL2 = 0x4111, // unknown yet
        TRI_FACEL1 = 0x4120,
        TRI_SMOOTH = 0x4150,
        TRI_LOCAL = 0x4160,
        TRI_VISIBLE = 0x4165,
        TRI_MATERIAL = 0x4130,

        //---- sub defs of KEYF3DS
        KEYF_UNKNWN01 = 0xB009,
        KEYF_UNKNWN02 = 0xB00A,
        KEYF_FRAMES = 0xB008,
        KEYF_OBJDES = 0xB002,

        KEYF_OBJHIERARCH = 0xB010,
        KEYF_OBJDUMMYNAME = 0xB011,
        KEYF_OBJUNKNWN01 = 0xB013,
        KEYF_OBJUNKNWN02 = 0xB014,
        KEYF_OBJUNKNWN03 = 0xB015,
        KEYF_OBJPIVOT = 0xB020,
        KEYF_OBJUNKNWN04 = 0xB021,
        KEYF_OBJUNKNWN05 = 0xB022,

        //----  these define the different color chunk types
        COL_RGB = 0x0010,
        COL_TRU = 0x0011,
        COL_UNK = 0x0013,// unknown

        //---- defines for viewport chunks
        TOP = 0x0001,
        BOTTOM = 0x0002,
        LEFT = 0x0003,
        RIGHT = 0x0004,
        FRONT = 0x0005,
        BACK = 0x0006,
        USER = 0x0007,
        CAMERA = 0x0008, // 0xFFFF is the code read from file
        LIGHT = 0x0009,
        DISABLED = 0x0010,
        BOGUS = 0x0011
    }

    #endregion

    #region Chunk

    internal class Chunk
    {
        public ushort ID;
        public uint Length;
        public int BytesRead;

        public Chunk(BinaryReader reader)
        {
            // 2 byte ID
            ID = reader.ReadUInt16();

            // 4 byte length
            Length = reader.ReadUInt32();

            // = 6
            BytesRead = 6;
        }
    }

    #endregion

    #region MaterialGroup

    /// <summary>
    /// Associate a set of triangles to a specific material.
    /// </summary>
    sealed public class MaterialGroup
    {
        public Material Material;
        public List<int> FacesIds = new List<int>();
    }

    #endregion

}
